package com.guoj.webmagic.service;

import com.guoj.webmagic.vo.HouseData;
import com.guoj.webmagic.vo.SearchResult;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * elasticsearch搜索服务
 */
@Service
public class SearchService {

	@Autowired
	private ElasticsearchTemplate elasticsearchTemplate;
	// 设定每页搜索出来的数据为10行，不对外暴露，用户也不能自己更改查看多少条。
	public static final Integer ROWS = 10;

	/**
	 * 根据关键字搜索返回数据，分页。
	 * @param keyword 关键字
	 * @param page 页码
	 * @return
	 */
	public SearchResult search(String keyword, Integer page) {
		PageRequest pageRequest = PageRequest.of(page - 1, ROWS); //设置分页参数，这里一定要-1.
		SearchQuery searchQuery = new NativeSearchQueryBuilder()
//				.withQuery(QueryBuilders.matchQuery("title", keyword).operator(Operator.AND)) // match查询，分词之间用AND。
				.withQuery(QueryBuilders.multiMatchQuery(keyword, "title", "title.pinyin").operator(Operator.AND)) // match查询，分词之间用AND。
				.withPageable(pageRequest)
				.withHighlightFields(new HighlightBuilder.Field("title")
										.preTags("<em style='color:red'>")
										.postTags("</em>")
										.fragmentSize(100)
				) // 设置高亮
				.build();

		// 搜索分页结果
		AggregatedPage<HouseData> housePage = this.elasticsearchTemplate.queryForPage(searchQuery, HouseData.class, new SearchResultMapper() {
			// 对高亮字段做处理
			@Override
			public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {

				// 若没命中，就返回一个空list。
				if (response.getHits().totalHits == 0) {
					return new AggregatedPageImpl<>(Collections.emptyList(), pageable, 0L);
				}

				List<T> list = new ArrayList<>();
				// 遍历搜索命中的对象
				for (SearchHit searchHit : response.getHits().getHits()) {
					T obj = (T) ReflectUtils.newInstance(clazz); // 反射获取对象

					try {
						// 手动写入上id，要加上forceAccess参数为true，否则会是null。
						FieldUtils.writeField(obj, "id", searchHit.getId(), true);
					} catch (IllegalAccessException e) {
						e.printStackTrace();
					}

					// 非高亮字段的数据写入
					for (Map.Entry<String, Object> entry : searchHit.getSourceAsMap().entrySet()) {

						Field field = FieldUtils.getField(clazz, entry.getKey(), true);
						if (null == field) {
							continue; // 是空就直接下一个
						}

						try {
							FieldUtils.writeField(obj, entry.getKey(), entry.getValue(), true);
						} catch (IllegalAccessException e) {
							e.printStackTrace();
						}
					}

					// 写入高亮的内容
					for (Map.Entry<String, HighlightField> entry : searchHit.getHighlightFields().entrySet()) {
						StringBuilder sb = new StringBuilder();
						Text[] fragments = entry.getValue().getFragments();
						for (Text fragment : fragments) {
							sb.append(fragment.toString());
							System.out.println("============= 高亮字段 =============");
							System.out.println(sb.toString());
							System.out.println("============= 高亮字段 =============");
						}

						// 写入高亮的内容
						try {
							FieldUtils.writeField(obj, entry.getKey(), sb.toString(), true);
						} catch (IllegalAccessException e) {
							e.printStackTrace();
						}
					}

					// 对象放入list中
					list.add(obj);

				}

				return new AggregatedPageImpl<>(list, pageable, response.getHits().totalHits);
			}
		});

		return new SearchResult(housePage.getTotalPages(), housePage.getContent());
	}

}